package ch.ethz.fcl.metrobuzz.data.ltt.node;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.vecmath.Vector2d;

import org.lwjgl.opengl.GL11;

import ch.ethz.fcl.metrobuzz.MBContainer;
import ch.ethz.fcl.metrobuzz.data.MBData;
import ch.ethz.fcl.metrobuzz.data.interchange.Interchange;
import ch.ethz.fcl.metrobuzz.data.ltt.TrajectoryGraph;
import ch.ethz.fcl.metrobuzz.data.scene.basemap.Node;
import ch.ethz.fcl.metrobuzz.render.techniques.circos.CircosDiagram;
import ch.ethz.fcl.metrobuzz.render.techniques.circos.CircosMatrix;
import ch.ethz.fcl.metrobuzz.render.techniques.circos.version5.CircosDiagram5;
import ch.ethz.fcl.metrobuzz.render.techniques.colors.MBColorTable;
import ch.ethz.fcl.metrobuzz.spatialindex.object2d.Point2D;
import ch.ethz.fcl.metrobuzz.tools.MBGL;
import ch.ethz.fcl.metrobuzz.tools.MBTools;

public class LTTNodeInterchange extends Interchange {
	private MBContainer container;
	private TrajectoryGraph trajGraph;

	private LTTNode lttNode = null;

	List<LTTNodeRibbon> ribbons;
	private Map<Integer, Integer> nodeIdToIndexMap;

	public LTTNodeInterchange(MBContainer container, TrajectoryGraph trajGraph,
			LTTNode node) {
		this.lttNode = node;
		this.container = container;
		this.trajGraph = trajGraph;

		ribbons = new ArrayList<LTTNodeRibbon>();
		nodeIdToIndexMap = new HashMap<Integer, Integer>();
		nodeIdToIndexMap.put(node.getId(), 0);
	}

	public void addInterchange(int fromNodeId, int toNodeId, int frequency) {
		Integer fromNodeIndex = nodeIdToIndexMap.get(fromNodeId);
		if (fromNodeIndex == null) {
			nodeIdToIndexMap.put(fromNodeId, nodeIdToIndexMap.size());
			fromNodeIndex = nodeIdToIndexMap.size();
		}

		Integer toNodeIndex = nodeIdToIndexMap.get(toNodeId);
		if (toNodeIndex == null) {
			nodeIdToIndexMap.put(toNodeId, nodeIdToIndexMap.size());
			toNodeIndex = nodeIdToIndexMap.size();
		}

		LTTNodeRibbon newRibbon = new LTTNodeRibbon(fromNodeId, toNodeId);

		int index = -1;
		for (int i = 0; i < ribbons.size(); i++) {
			if (ribbons.get(i).equals(newRibbon)) {
				index = i;
				break;
			}
		}

		if (index == -1) {
			newRibbon.addFrequency(frequency);
			ribbons.add(newRibbon);
		} else
			ribbons.get(index).addFrequency(frequency);
	}

	private void init() {
		MBData data = container.getData();
		CircosMatrix matrix = new CircosMatrix(nodeIdToIndexMap.size());

		// set matrix value
		for (int i = 0; i < ribbons.size(); i++) {
			LTTNodeRibbon ribbon = ribbons.get(i);
			int from = ribbon.getFrom();
			int to = ribbon.getTo();

			matrix.setData(nodeIdToIndexMap.get(from),
					nodeIdToIndexMap.get(to), ribbon.getFrequency());
		}

		matrix.setColor(0, MBColorTable.LIGHTGREY);

		// set matrix direction
		Node thisNode = data.getScene().getNode(lttNode.getId());
		for (Integer connectedNodeId : nodeIdToIndexMap.keySet()) {
			if (connectedNodeId != lttNode.getId()) {
				Node otherNode = data.getScene().getNode(connectedNodeId);
				Vector2d dir = new Vector2d(otherNode.getX() - thisNode.getX(),
						otherNode.getY() - thisNode.getY());

				int matrixIndex = nodeIdToIndexMap.get(connectedNodeId);
				matrix.getDirections().setDirection(matrixIndex, dir);
			}
		}

		CircosDiagram circosDiagram = new CircosDiagram5(thisNode.toPoint(),
				0.01f, matrix);

		setCircosDiagram(circosDiagram);

		if (lttNode.toNode().getId().equals("EW24/NS1")) {
			for (Integer nodeId : this.getConnectedNodeIds()) {
				LTTNode node = trajGraph.getSelectedLTTNodeMap().get(nodeId);
				System.out.println("connected node " + node.toNode().getId()
						+ " ");
				int index = getConnectedNodeIndex(node.getId());
				System.out.println("incoming: " + matrix.getIncoming()[index]
						+ " outgoing: " + matrix.getOutgoing()[index] + "\n");
			}
		}
	}

	// render all the ribbons from this node to the end node as arrow
	public void renderAsArrow() {
		List<LTTNodeRibbon> fromNodeRibbons = new ArrayList<LTTNodeRibbon>();
		for (int i = 0; i < ribbons.size(); i++) {
			LTTNodeRibbon asso = ribbons.get(i);
			int to = asso.getTo();

			LTTNodeRibbon fromNodeAsso = new LTTNodeRibbon(lttNode.getId(), to);
			int index = -1;
			for (int j = 0; j < fromNodeRibbons.size(); j++) {
				if (fromNodeRibbons.get(j).equals(fromNodeAsso)) {
					index = j;
					break;
				}
			}

			if (index == -1) {
				fromNodeAsso.addFrequency(asso.getFrequency());
				fromNodeRibbons.add(fromNodeAsso);
			} else
				fromNodeRibbons.get(index).addFrequency(asso.getFrequency());
		}

		float onePixelDist = container.getViewer().getProjection()
				.getWorldDistance(1);
		for (int i = 0; i < fromNodeRibbons.size(); i++) {
			LTTNodeRibbon asso = fromNodeRibbons.get(i);

			Node from = container.getData().getScene().getNode(asso.getFrom());
			Node to = container.getData().getScene().getNode(asso.getTo());
			float dist = MBTools.distance(from.getX(), from.getY(), to.getX(),
					to.getY());

			Vector2d dir = new Vector2d(to.getX() - from.getX(), to.getY()
					- from.getY());
			Vector2d rightAngleDir = MBTools.rotate(dir, Math.PI / 2);

			Point2D p0 = from.toPoint().getPointAt(rightAngleDir, onePixelDist);
			Point2D p1 = to.toPoint().getPointAt(rightAngleDir, onePixelDist);

			Point2D p2 = p0.getPointAt(rightAngleDir, onePixelDist * 4);
			Point2D p3 = p2.getPointAt(dir, dist * 3 / 4);

			Point2D p4 = p3.getPointAt(rightAngleDir, dist / 8);

			MBColorTable.assignColor(MBColorTable.LIGHTGREY, 1.0f);
			GL11.glLineWidth(1.0f);
			GL11.glBegin(GL11.GL_LINES);
			MBGL.render(from.toPoint());
			MBGL.render(to.toPoint());
			GL11.glEnd();

			// float[] c = LTTColor.getColor(asso.getFrequency());
			float[] c = { 0.1f, 0.1f, 0.1f, 0.8f };
			GL11.glColor4f(c[0], c[1], c[2], c[3]);

			Point2D[] g1 = { p0, p1, p3, p2 };
			MBGL.render(g1, GL11.GL_POLYGON);

			Point2D[] g2 = { p1, p3, p4 };
			MBGL.render(g2, GL11.GL_TRIANGLES);

			Point2D[] g3 = { p0, p1, p4, p3, p2, p0 };
			GL11.glColor3f(0, 0, 0);
			GL11.glLineWidth(1.0f);
			MBGL.render(g3, GL11.GL_LINE_STRIP);
		}
	}

	public void renderAsCircle(float maxValue) {
		if (this.cd == null)
			init();

		float passingFlowVolume = cd.getData().getTotalSum();

		float startEndFlowVolume = cd.getData().getIncoming()[0]
				+ cd.getData().getOutgoing()[0];

		float outRadius = (float) (passingFlowVolume / maxValue
				* cd.getFlyoverRingRadius() * Math.PI / 2);
		float inRadius = (float) (outRadius * Math.sqrt(startEndFlowVolume
				/ passingFlowVolume));

		Node node = container.getData().getScene().getNode(lttNode.getId());

		// render outside circle as light grey
		// ColorTable.assignColor(ColorTable.LIGHTGREY, 1.0f);
		int index = trajGraph.getLTTNodeIndex(lttNode);
		MBColorTable.assignColor(index, 1.0f);
		MBGL.renderEclipse(node.getX(), node.getY(), 0, outRadius, outRadius,
				true);

		MBColorTable.assignColor(MBColorTable.WHITE, 1.0f);
		MBGL.renderEclipse(node.getX(), node.getY(), 0, inRadius, inRadius,
				true);

		MBColorTable.assignColor(MBColorTable.BLACK, 1.0f);
		GL11.glLineWidth(1.0f);
		MBGL.renderEclipse(node.getX(), node.getY(), 0, outRadius, outRadius,
				false);
		MBGL.renderEclipse(node.getX(), node.getY(), 0, inRadius, inRadius,
				false);
	}

	// render the origin / destination ratio
	// blue as orgin, green as destination
	public void renderStartEndRatio() {
		if (this.cd == null)
			init();

		float outRadius = circosRadius
				+ container.getViewer().getProjection().getWorldDistance(4);

		Node center = container.getData().getScene().getNode(lttNode.getId());
		int slices = 100;
		int outIndex = (int) (cd.getData().getOutgoing()[0]
				/ (cd.getData().getOutgoing()[0] + cd.getData().getIncoming()[0]) * slices);
		// dark blue
		GL11.glColor3f(33.0f / 255, 19.0f / 255, 247.0f / 255);
		GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
		for (int i = 0; i <= outIndex; i++) {
			float inX = (float) (center.getX() + circosRadius
					* Math.cos(Math.PI * 2 / slices * i));
			float inY = (float) (center.getY() + circosRadius
					* Math.sin(Math.PI * 2 / slices * i));

			float outX = (float) (center.getX() + outRadius
					* Math.cos(Math.PI * 2 / slices * i));
			float outY = (float) (center.getY() + outRadius
					* Math.sin(Math.PI * 2 / slices * i));

			GL11.glVertex3f(inX, inY, 0);
			GL11.glVertex3f(outX, outY, 0);
		}
		GL11.glEnd();

		GL11.glColor3f(0, 215.0f / 255, 59.0f / 255); // green
		GL11.glBegin(GL11.GL_TRIANGLE_STRIP);
		for (int i = outIndex; i <= slices; i++) {
			float inX = (float) (center.getX() + circosRadius
					* Math.cos(Math.PI * 2 / slices * i));
			float inY = (float) (center.getY() + circosRadius
					* Math.sin(Math.PI * 2 / slices * i));

			float outX = (float) (center.getX() + outRadius
					* Math.cos(Math.PI * 2 / slices * i));
			float outY = (float) (center.getY() + outRadius
					* Math.sin(Math.PI * 2 / slices * i));

			GL11.glVertex3f(inX, inY, 0);
			GL11.glVertex3f(outX, outY, 0);
		}
		GL11.glEnd();

		MBColorTable.assignColor(MBColorTable.BLACK, 1.0f);
		MBGL.renderEclipse(center.getX(), center.getY(), 0, circosRadius,
				circosRadius, false);
		MBGL.renderEclipse(center.getX(), center.getY(), 0, outRadius,
				outRadius, false);
	}

	// render all the associations from this node to the end node as circos
	// diagram
	public void renderAsCircos() {
		if (this.cd == null)
			init();
		cd.render(null);
	}

	public Point2D getConnectPoint(int index, boolean isOut) {
		Vector2d midDir = cd.getData().getDirections().getRepelDirections()[index];

		float outgoing = cd.getData().getOutgoing()[index];
		float incoming = cd.getData().getIncoming()[index];

		float inAngle = cd.getRenderAngle()
				* incoming
				/ (cd.getData().getTotalSum() - cd.getData().getIncoming()[0] - cd
						.getData().getOutgoing()[0]);
		float outAngle = cd.getRenderAngle()
				* outgoing
				/ (cd.getData().getTotalSum() - cd.getData().getIncoming()[0] - cd
						.getData().getOutgoing()[0]);

		Vector2d dir = null;
		// if (isOut)
		// dir = MBTools.rotate(midDir, inAngle / 2);
		// else
		dir = MBTools.rotate(midDir, -outAngle / 2 + inAngle / 2);

		return lttNode.toNode().toPoint()
				.getPointAt(dir, cd.getRadius() * 1.08f);
	}

	public int getConnectedNodeIndex(int nodeId) {
		return nodeIdToIndexMap.get(nodeId);
	}

	public List<Integer> getConnectedNodeIds() {
		List<Integer> nodeIds = new ArrayList<Integer>(
				nodeIdToIndexMap.keySet());

		return nodeIds;
	}

	@Override
	public CircosDiagram getCircos() {
		if (cd == null)
			init();

		return cd;
	}

	public LTTNode getLTTNode() {
		return lttNode;
	}

	class LTTNodeRibbon {
		private int from;
		private int to;
		private int frequency;

		public LTTNodeRibbon(int from, int to) {
			this.from = from;
			this.to = to;
		}

		public void addFrequency(int fre) {
			this.frequency += fre;
		}

		public int getFrom() {
			return from;
		}

		public int getTo() {
			return to;
		}

		public int getFrequency() {
			return frequency;
		}

		public String toString(MBData data) {
			String fromId = data.getScene().getNode(from).getId();
			String toId = data.getScene().getNode(to).getId();
			return "Ribbon " + fromId + "->" + toId + ": " + frequency;
		}

		@Override
		public String toString() {
			return "Ribbon " + from + "->" + to + ": " + frequency;
		}

		@Override
		public boolean equals(Object o) {
			if (!(o instanceof LTTNodeRibbon))
				return false;
			LTTNodeRibbon a = (LTTNodeRibbon) o;
			if (this.from == a.from && this.to == a.to)
				return true;

			return false;
		}
	}
}
